msg_tool\scripts\favorite/
hcb.rs

1//! Favorite HCB script (.hcb)
2use super::disasm::*;
3use crate::ext::io::*;
4use crate::scripts::base::*;
5use crate::types::*;
6use crate::utils::encoding::*;
7use anyhow::Result;
8
9#[derive(Debug)]
10/// Favorite HCB script builder
11pub struct HcbScriptBuilder {}
12
13impl HcbScriptBuilder {
14    /// Create a new HcbScriptBuilder
15    pub fn new() -> Self {
16        Self {}
17    }
18}
19
20impl ScriptBuilder for HcbScriptBuilder {
21    fn default_encoding(&self) -> Encoding {
22        Encoding::Cp932
23    }
24
25    fn build_script(
26        &self,
27        buf: Vec<u8>,
28        _filename: &str,
29        encoding: Encoding,
30        _archive_encoding: Encoding,
31        config: &ExtraConfig,
32        _archive: Option<&Box<dyn Script>>,
33    ) -> Result<Box<dyn Script>> {
34        Ok(Box::new(HcbScript::new(buf, encoding, config)?))
35    }
36
37    fn extensions(&self) -> &'static [&'static str] {
38        &["hcb"]
39    }
40
41    fn script_type(&self) -> &'static ScriptType {
42        &ScriptType::Favorite
43    }
44}
45
46#[derive(Debug)]
47pub struct HcbScript {
48    data: Data,
49    reader: MemReader,
50    custom_yaml: bool,
51    filter_ascii: bool,
52    encoding: Encoding,
53}
54
55impl HcbScript {
56    pub fn new(buf: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
57        let reader = MemReader::new(buf);
58        let data = Data::disasm(reader.to_ref(), encoding)?;
59        Ok(Self {
60            data,
61            reader,
62            custom_yaml: config.custom_yaml,
63            filter_ascii: config.favorite_hcb_filter_ascii,
64            encoding,
65        })
66    }
67}
68
69impl Script for HcbScript {
70    fn default_output_script_type(&self) -> OutputScriptType {
71        OutputScriptType::Json
72    }
73
74    fn default_format_type(&self) -> FormatOptions {
75        FormatOptions::None
76    }
77
78    fn is_output_supported(&self, _: OutputScriptType) -> bool {
79        true
80    }
81
82    fn custom_output_extension<'a>(&'a self) -> &'a str {
83        if self.custom_yaml { "yaml" } else { "json" }
84    }
85
86    fn extract_messages(&self) -> Result<Vec<Message>> {
87        let mut messages = Vec::new();
88        for funcs in [&self.data.functions, &self.data.main_script] {
89            for func in funcs {
90                for operand in &func.operands {
91                    if let Operand::S(s) = operand {
92                        if self.filter_ascii && s.chars().all(|c| c.is_ascii()) {
93                            continue;
94                        }
95                        messages.push(Message::new(s.clone(), None));
96                    }
97                }
98            }
99        }
100        Ok(messages)
101    }
102
103    fn import_messages<'a>(
104        &'a self,
105        messages: Vec<Message>,
106        file: Box<dyn WriteSeek + 'a>,
107        _filename: &str,
108        encoding: Encoding,
109        replacement: Option<&'a ReplacementTable>,
110    ) -> Result<()> {
111        let mut mess = messages.iter();
112        let mut mes = mess.next();
113        let mut patcher =
114            BinaryPatcher::new(self.reader.to_ref(), file, |pos| Ok(pos), |pos| Ok(pos))?;
115        let mut need_pacth_addresses = Vec::new();
116        for funcs in [&self.data.functions, &self.data.main_script] {
117            for func in funcs {
118                let mut cur_pos = func.pos + 1;
119                if matches!(func.opcode, 0x02 | 0x06 | 0x07) {
120                    need_pacth_addresses.push(cur_pos);
121                }
122                for operand in &func.operands {
123                    if let Operand::S(s) = operand {
124                        if self.filter_ascii && s.chars().all(|c| c.is_ascii()) {
125                            continue;
126                        }
127                        let m = match mes {
128                            Some(m) => m,
129                            None => {
130                                return Err(anyhow::anyhow!(
131                                    "Not enough messages to import. Missing message: {}",
132                                    s
133                                ));
134                            }
135                        };
136                        let mut message = m.message.clone();
137                        if let Some(table) = replacement {
138                            for (k, v) in &table.map {
139                                message = message.replace(k, v);
140                            }
141                        }
142                        patcher.copy_up_to(cur_pos)?;
143                        let ori_len = operand.len(self.encoding)? as u64;
144                        let mut s = encode_string(encoding, &message, true)?;
145                        s.push(0); // null-terminated
146                        let len = s.len();
147                        if len > 255 {
148                            return Err(anyhow::anyhow!(
149                                "Message too long to import (max 255 bytes): {}",
150                                message
151                            ));
152                        }
153                        patcher.replace_bytes_with_write(ori_len, |writer| {
154                            writer.write_u8(len as u8)?;
155                            writer.write_all(&s)?;
156                            Ok(())
157                        })?;
158                        mes = mess.next();
159                    }
160                    cur_pos += operand.len(self.encoding)? as u64;
161                }
162            }
163        }
164        patcher.copy_up_to(self.reader.data.len() as u64)?;
165        for addr in need_pacth_addresses {
166            patcher.patch_u32_address(addr)?;
167        }
168        Ok(())
169    }
170
171    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
172        let s = if self.custom_yaml {
173            serde_yaml_ng::to_string(&self.data)?
174        } else {
175            serde_json::to_string_pretty(&self.data)?
176        };
177        let e = encode_string(encoding, &s, false)?;
178        let mut writer = crate::utils::files::write_file(filename)?;
179        writer.write_all(&e)?;
180        Ok(())
181    }
182}